/**
 * 
 */
package view.impl.ingame.animation;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FilenameFilter;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import java.util.Scanner;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.newdawn.slick.Animation;
import org.newdawn.slick.Image;
import org.newdawn.slick.SlickException;
import org.newdawn.slick.SpriteSheet;
import org.newdawn.slick.particles.ConfigurableEmitter;
import org.newdawn.slick.particles.ParticleIO;
import org.newdawn.slick.particles.ParticleSystem;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import util.ResLoc;
import view.impl.ingame.Fighter;
import view.impl.ingame.Fighter.Player;


/**
 * @author Julian
 *
 */
public class Effect {
	/*
	 * Images are non-mutable and can't be duplicated. Therefore, no caching is 
	 * needed => KLAD PROKRAMMERARE \o/ 
	 */
	private static final int CHARACTER_WIDTH = 70;
	
	//private String name = null;
	
	private List<EffectImage> images = new LinkedList<EffectImage>();
	private List<Projectile> projectiles = new LinkedList<Projectile>();
	private ParticleSystem psystem = null;
	private int psystemDur = 0;
	
	private int playTime = 0;
	
	private int posX = 0;
	private int posY = 0;
	
	private boolean success = false;
	
	private boolean pDead = false;	// Is the particle system dead?
	
	static private void loadEffects() {
		
	}
	
	public Effect(String effectName, Fighter owner) throws EffectLoadException {
		loadEffect(effectName, owner);
	}
	
	private void loadEffect(String effectName, Fighter owner) throws EffectLoadException {
		
		if(effectName.equals("")) {
			
			String effectFolder;
			try {
				effectFolder = ResLoc.getResource("InGame.effects") + "/" + effectName;
			} catch (FileNotFoundException e) {
				throw new EffectLoadException(e);
			}
			
			DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
			DocumentBuilder docBuilder;
			try {
				docBuilder = docBuilderFactory.newDocumentBuilder();
			} catch (ParserConfigurationException e) {
				throw new EffectLoadException(e);
			}
			Document doc;
			try {
				doc = docBuilder.parse(new File(effectFolder + "/info.xml"));
			} catch (SAXException e) {
				throw new EffectLoadException(e);
			} catch (IOException e) {
				throw new EffectLoadException(e);
			}
			
			doc.getDocumentElement().normalize();
			
			//name = doc.getDocumentElement().getAttribute("name");
			
			// Load EffectImage:s
			NodeList nlImages = doc.getElementsByTagName("image");
			
			for(int i = 0; i < nlImages.getLength(); i++) {
				Node imageNode = nlImages.item(i);
				
				if(imageNode.getNodeType() == Node.ELEMENT_NODE) {
					Element imageElement = (Element)imageNode;
					
					String src = effectFolder + "/" + imageElement.getAttribute("src");
					float sizeDiff = Float.parseFloat(imageElement.getAttribute("sizeDiff"));
					float rotDiff = Float.parseFloat(imageElement.getAttribute("rotDiff"));
					int duration = Integer.parseInt(imageElement.getAttribute("duration"));
					
					/*
					// Load image position.
					Element posElement = (Element)imageElement.getElementsByTagName("pos").item(0);
					String[] pos = posElement.getChildNodes().item(0).getNodeValue().split(";");
					int posX = Integer.parseInt(pos[0].trim());
					int posY = Integer.parseInt(pos[1].trim());
					*/
					//TODO: Parse position expression. 
					
					try {
						images.add(new EffectImage(src, sizeDiff, rotDiff, duration));
					} catch (SlickException e) {
						throw new EffectLoadException(e);
					}
				}
			}
			
			// Load particle emitters
			NodeList nlEmitters = doc.getElementsByTagName("emitter");
			
			for(int i = 0; i < nlEmitters.getLength(); i++) {
				Node emitterNode = nlEmitters.item(i);
				
				if(emitterNode.getNodeType() == Node.ELEMENT_NODE) {
					Element emitterElement = (Element)emitterNode;
					
					String src = effectFolder + "/" + emitterElement.getAttribute("src");
					psystemDur = Integer.parseInt(emitterElement.getAttribute("duration"));
					
					// Load image position.
					//Element posElement = (Element)emitterElement.getElementsByTagName("pos").item(0);
					//String[] pos = posElement.getChildNodes().item(0).getNodeValue().split(";");
					//String posXStr = pos[0].trim();
					//String posYStr = pos[1].trim();
					
					//TODO: parse expression in posXStr and posYStr.
					ConfigurableEmitter emitter;
					try {
						emitter = ParticleIO.loadEmitter(new File(src));
					} catch (IOException e) {
						throw new EffectLoadException(e);
					}

					try {
						psystem = new ParticleSystem(ResLoc.getResource("InGame.defaultParticle"));
					} catch (FileNotFoundException e) {
						throw new EffectLoadException(e);
					}
					psystem.addEmitter(emitter);
				}
			}
			
			// Load projectiles
			NodeList nlProj = doc.getElementsByTagName("projectile");
			
			for(int i = 0; i < nlProj.getLength(); i++) {
				Node projNode = nlProj.item(i);
				
				if(projNode.getNodeType() == Node.ELEMENT_NODE) {
					Element projElement = (Element)projNode;
					
					String src = effectFolder + "/" + projElement.getAttribute("src");
					
					// Load image position.
					Element posElement = (Element)projElement.getElementsByTagName("pos").item(0);
					String[] pos = posElement.getChildNodes().item(0).getNodeValue().split(";");
					String posXStr = pos[0].trim();
					String posYStr = pos[1].trim();
					
					int relCastPosX = Integer.parseInt(posXStr);
					int relCastPosY = Integer.parseInt(posYStr);

					projectiles.add(new Projectile(src, relCastPosX, relCastPosY, owner));
				}
			}
		}
	}
	
	public void setPos(int posX, int posY) {
		this.posX = posX;
		this.posY = posY;
	}

	public void setSuccess(boolean success) {
		this.success = success;
	}
	
	void draw(int posX, int posY, int delta) {
		playTime += delta;
		
		if(psystem != null) {
			psystem.update(delta);
			psystem.render(posX, posY);
		}
		
		for(EffectImage img : images) {
			img.draw(posX, posY);
		}
	}
	
	public void draw(int delta, Fighter player1, Fighter player2) {
		playTime += delta;
		
		if(psystem != null) {
			psystem.update(delta);
			psystem.render(posX, posY);
		}
		
		if(playTime >= psystemDur)
			pDead = true;
		
		for(EffectImage img : images) {
			img.draw(posX, posY);
		}
		
		for(Projectile proj : projectiles) {
			proj.draw(delta, player1, player2);
		}
	}
	
	public void restart() {
		playTime = 0;
		for(EffectImage img : images) {
			img.restart();
		}
		
		if(psystem != null) {
			psystem.reset();
		}
		
		pDead = false;
	}
	
	public boolean isDead() {
		if(!pDead)
			return false;
		
		for(EffectImage img : images) {
			if(!img.isDead())
				return false;
		}
		for(Projectile proj : projectiles) {
			if(!proj.isDead())
				return false;
		}
		
		return false;
	}
	
	private class EffectImage {
		private List<Image> images = new LinkedList<Image>();
		
		//private int posX, posY;
		private float sizeDiff, rotDiff;
		
		private int duration = 0;
		private boolean started = false;
		
		private Image img = null;
		
		private EffectImage(String src, float sizeDiff, float rotDiff, int duration) throws SlickException{
			
			
			File srcFile = new File(src);
			
			if(srcFile.isFile()) {
				images.add(new Image(src));
			}
			else {
				String[] allImages = srcFile.list(new FilenameFilter() {
						@Override
						public boolean accept(File dir, String name) {
							return !name.startsWith(".");
						}
					});
				
				for(String imgSrc : allImages) {
					images.add(new Image(src + imgSrc));
				}
			}
			
			this.sizeDiff = sizeDiff;
			this.rotDiff = rotDiff;
			this.duration = duration;
		}
		
		/*
		private void setPos(int posX, int posY) { 
			this.posX = posX;
			this.posY = posY;
		}
		*/
		
		private void draw(int posX, int posY) {
			if(playTime <= duration) {
				
				if(started == false) {
					int imgNb = (int) (images.size() * Math.random());
					img = images.get(imgNb);
					img.setRotation((float) (rotDiff - 2 * rotDiff * Math.random()));
					started = true;
				}
				
				img.draw(posX - img.getWidth() / 2, posY - img.getHeight() / 2, 1f + sizeDiff);
			}
		}
		
		private void restart() {
			started = false;
		}
		
		private boolean isDead() {
			if(!success) {
				return true;
			}
			return playTime <= duration;
		}
		
	}
	
	private class Projectile {
		
		private Fighter owner;
		
		private Animation castAnim, flyAnim, hitAnim;
		
		private int castPosX = 0;
		private int castPosY = 0;
		private int relCastPosX = 0;
		private int relCastPosY = 0;
		
		
		private int posX = 0;
		//private int posY = 0;
		private int speed = 0;
		
		private Projectile(String src, int relCastPosX, int relCastPosY, Fighter owner) throws EffectLoadException {
			this.owner = owner;
			this.castPosX = owner.getPlayer() == Player.PLAYER1 ? owner.getPosX() + relCastPosX : 
																  owner.getPosX() - relCastPosX;
			this.castPosY = owner.getPosY() - relCastPosY;
			this.relCastPosX = relCastPosX;
			this.relCastPosY = relCastPosY;
			this.posX = castPosX;
			//this.posY = castPosY;
			
			// Load all the animations.
			try {
				Scanner sc = new Scanner(new File(src + "/info.txt"));
				
				//int startFrame = 0;
				int castFrames = 0;
				int castDuration = 0;
				int flyFrames = 0;
				int flyDuration = 0;
				int hitFrames = 0;
				int hitDuration = 0;
				
				while(sc.hasNextLine()) {
					String[] nl = sc.nextLine().split(":");
					
					String arg = nl[0].toLowerCase().trim();
					String val = nl[1].toLowerCase().trim();
					
					if(arg.equals("startFrame")) {
						//startFrame = Integer.parseInt(val);
						
					} else if(arg.equals("speed")) {
						speed = Integer.parseInt(val);
						
					} else if(arg.equals("castframes")) {
						castFrames = Integer.parseInt(val);
						
					} else if(arg.equals("castduration")) {
						castDuration = Integer.parseInt(val);
						
					} else if(arg.equals("flyframes")) {
						flyFrames = Integer.parseInt(val);
						
					} else if(arg.equals("flyduration")) {
						flyDuration = Integer.parseInt(val);
						
					} else if(arg.equals("hitframes")) {
						hitFrames = Integer.parseInt(val);
						
					} else if(arg.equals("hitduration")) {
						hitDuration = Integer.parseInt(val);
						
					} /*else if(arg.equals("shootposx")) {
						startPosX += Integer.parseInt(val);
						this.posX = startPosX;
						
					} else if(arg.equals("shootposy")) {
						startPosY += Integer.parseInt(val);
						this.posY = startPosY;
						
					}*/
				}
				
				Image castImg = new Image(src + "/castframes.png");
				Image flyImg = new Image(src + "/flyframes.png");
				Image hitImg = new Image(src + "/hitframes.png");
				
				
				SpriteSheet castSS = new SpriteSheet(castImg, castImg.getWidth() / castFrames, castImg.getHeight());
				SpriteSheet flySS = new SpriteSheet(flyImg, flyImg.getWidth() / flyFrames, flyImg.getHeight());
				SpriteSheet hitSS = new SpriteSheet(hitImg, hitImg.getWidth() / hitFrames, hitImg.getHeight());
				
				if(owner.getPlayer() == Player.PLAYER1) {
					castAnim = new Animation(castSS, castDuration);
					castAnim.setLooping(false);
					
					flyAnim = new Animation(flySS, flyDuration);
					flyAnim.setLooping(true);
					
					hitAnim = new Animation(hitSS, hitDuration);
					hitAnim.setLooping(false);
				} else {
					Image[] mirroredCastFrames = new Image[castFrames];
					Image[] mirroredFlyFrames = new Image[flyFrames];
					Image[] mirroredHitFrames = new Image[hitFrames];
					
					for(int i = 0; i < castFrames; i++) {
						mirroredCastFrames[i] = castSS.getSprite(i, 0).getFlippedCopy(true, false);
					}
					for(int i = 0; i < flyFrames; i++) {
						mirroredFlyFrames[i] = flySS.getSprite(i, 0).getFlippedCopy(true, false);
					}
					for(int i = 0; i < hitFrames; i++) {
						mirroredHitFrames[i] = hitSS.getSprite(i, 0).getFlippedCopy(true, false);
					}
					
					castAnim = new Animation(mirroredCastFrames, castDuration);
					castAnim.setLooping(false);
					
					flyAnim = new Animation(mirroredFlyFrames, flyDuration);
					flyAnim.setLooping(true);
					
					hitAnim = new Animation(mirroredHitFrames, hitDuration);
					hitAnim.setLooping(false);
				}
				
			} catch (FileNotFoundException e) {
				throw new EffectLoadException(e);
			} catch (SlickException e) {
				throw new EffectLoadException(e);
			}
		}
		
		/*
		private void setCastPos(int relCastPosX, int relCastPosY) {
			this.castPosX = owner.getPlayer() == Player.PLAYER1 ? owner.getPosX() + relCastPosX : 
				  												  owner.getPosX() - relCastPosX;
			this.castPosY = owner.getPosY() - relCastPosY;
			
			System.out.println("Projectile.setCastPos:\nstartPosX: " + castPosX + ", startPosY: " + castPosY + "\n");
		}
		*/
		
		private void draw(int delta, Fighter player1, Fighter player2) {
			Fighter opponent = null;
			if(player1 == owner) {
				opponent = player2;
			} else {
				opponent = player1;
			}
			
			if(!castAnim.isStopped()) {
				castAnim.draw(castPosX - castAnim.getWidth() / 2, castPosY - castAnim.getHeight());
				
			} else if( (owner.getPlayer() == Player.PLAYER1 && posX < opponent.getPosX() - CHARACTER_WIDTH) ||
					   (owner.getPlayer() == Player.PLAYER2 && posX > opponent.getPosX() + CHARACTER_WIDTH) ) {
				posX += speed * delta;	// TODO: Change to seconds?
				
				flyAnim.draw(posX - flyAnim.getWidth() / 2, castPosY - flyAnim.getHeight());
				
			} else if(!hitAnim.isStopped()) {
				if(hitAnim.isStopped()) {
					hitAnim.start();
				}
				hitAnim.draw(posX - hitAnim.getWidth() / 2, castPosY - hitAnim.getHeight());
				
			}
		}
		
		private void restart() {
			System.out.println("Projectile.restart:\nowner.getPosX(): " + owner.getPosX() + "\n");
			this.castPosX = owner.getPlayer() == Player.PLAYER1 ? owner.getPosX() + relCastPosX : 
																  owner.getPosX() - relCastPosX;
			this.castPosY = owner.getPosY() - relCastPosY;
			
			posX = castPosX;
			posY = castPosY;
			
			castAnim.restart();
		}
		
		private boolean isDead() {
			return hitAnim.isStopped();
		}
	}
}
